﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using AWS.SLAM.SharePoint;

namespace AWS.SLAM.Configuration
{
    public class XmlSqlDataMapping : DataMapping
    {
        #region Private State

        /// <summary>
        /// Regex pattern to validate Guid strings
        /// </summary>
        private static Regex _guidRegex = new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);

        /// <summary>
        /// Configuration file XDocument
        /// </summary>
        private XDocument _configXml;

        /// <summary>
        /// The Site Collection for which this DataMapping is defined
        /// </summary>
        private string _siteCollectionName = "";

        private bool _defaultDataMapping;
        private bool _defaultConfirmed;

        #endregion

        #region Constructors

        public XmlSqlDataMapping(XDocument configurationXml)
        {
            _configXml = configurationXml;
            _defaultDataMapping = true;
            _defaultConfirmed = false;
        }

        public XmlSqlDataMapping(XDocument configurationXml, string siteCollectionName)
        {
            _configXml = configurationXml;
            _siteCollectionName = siteCollectionName;
            _defaultDataMapping = false;
        }

        #endregion

        /// <summary>
        /// Gets all Types, Lists and ContentTypes, in the configuration sorted by ActiveationOrder.
        /// </summary>
        /// <returns>IEnumberable collection of ConfigTypes</returns>
        public override IEnumerable<ConfiguredType> GetAllTypes()
        {
            ConfirmDefaultDataMapping();

            IEnumerable<ConfiguredType> contentTypeNames =
                (from typeNode in _configXml.Root.Elements().Where(mapping => 
                    mapping.Name == "DataMapping" &&
                    (String.IsNullOrEmpty(_siteCollectionName) ? 
                    (mapping.Attribute("SiteCollection") == null || 
                    String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                    (mapping.Attribute("SiteCollection") != null && 
                    mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                 let activationOrder = int.Parse(typeNode.Attribute("ActivationOrder").Value)
                 orderby activationOrder
                 select new SqlConfiguredType
                 {
                     Name = typeNode.Attribute("Name") == null ? "" : (string)typeNode.Attribute("Name"),
                     Table = typeNode.Attribute("TableName") == null ? "" : (string)typeNode.Attribute("TableName"),
                     Id = typeNode.Attribute("ID") == null ? "" : (string)typeNode.Attribute("ID"),
                     ContentType = typeNode.Name.LocalName == "ContentType",
                     Site = typeNode.Attribute("Site") == null ? "" : (string)typeNode.Attribute("Site"),
                     SiteCollection = typeNode.Parent.Attribute("SiteCollection") == null ? "" : (string)typeNode.Parent.Attribute("SiteCollection"),
                 }).Cast<ConfiguredType>();

            return contentTypeNames;
        }

        /// <summary>
        /// Gets the database schema for this DataMapping stored in the configuration.
        /// </summary>
        /// <returns></returns>
        public string GetDataSchema()
        {
            ConfirmDefaultDataMapping();

            return _configXml.Root.Elements().Where(node => 
                    node.Name == "DataMapping" && 
                    (String.IsNullOrEmpty(_siteCollectionName) ? 
                    (node.Attribute("SiteCollection") == null || 
                    String.IsNullOrEmpty(node.Attribute("SiteCollection").Value)) :
                    (node.Attribute("SiteCollection") != null &&
                    node.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).FirstOrDefault().Attribute("DataSchema").Value;
        }

        /// <summary>
        /// Gets the connection string for this DataMapping stored in the configuration.
        /// </summary>
        /// <returns></returns>
        public string GetConnectionString()
        {
            ConfirmDefaultDataMapping();

            return (string)(_configXml.Root.Elements().Where(node =>
                    node.Name == "DataMapping" &&
                    (String.IsNullOrEmpty(_siteCollectionName) ?
                    (node.Attribute("SiteCollection") == null ||
                    String.IsNullOrEmpty(node.Attribute("SiteCollection").Value)) :
                    (node.Attribute("SiteCollection") != null &&
                    node.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).FirstOrDefault().Attribute("ConnectionString")) ?? "";
        }

        /// <summary>
        /// Gets fields that are associations for the given type id or name.
        /// </summary>
        /// <param name="id">Optional unique SharePoint List Guid id as string</param>
        /// <param name="typeName">Name of List or ContentType</param>
        /// <returns>IEnumberable collection of AssociationFields</returns>
        public override IEnumerable<AssociationField> GetTypeAssociations(string id, string typeName)
        {
            ConfirmDefaultDataMapping();

            Guid guidId = Guid.Empty;
            XElement selectedNode = null;

            //Selection precedence follows:
            //If ID is present and a valid, Get type that matches ID
            //If no type matches ID, Get type that has TableName matching the typeName
            //Finally, if nothing yet has been selected get the first node that has Name matching the 
            //typeName

            //Check if ID is present and valid
            if (!String.IsNullOrEmpty(id) && _guidRegex.IsMatch(id))
            {
                guidId = new Guid(id);

                selectedNode = (from typeNode in _configXml.Root.Elements().Where(mapping =>
                                   mapping.Name == "DataMapping" &&
                                   (String.IsNullOrEmpty(_siteCollectionName) ?
                                   (mapping.Attribute("SiteCollection") == null ||
                                   String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                   (mapping.Attribute("SiteCollection") != null &&
                                   mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                where typeNode.Attribute("ID") != null &&
                                      typeNode.Attribute("ID").Value == guidId.ToString()
                                select typeNode).DefaultIfEmpty().First();
            }

            if (selectedNode == null)
            {
                //Get node matching TableName
                selectedNode = (from typeNode in _configXml.Root.Elements().Where(mapping =>
                                   mapping.Name == "DataMapping" &&
                                   (String.IsNullOrEmpty(_siteCollectionName) ?
                                   (mapping.Attribute("SiteCollection") == null ||
                                   String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                   (mapping.Attribute("SiteCollection") != null &&
                                   mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                where typeNode.Attribute("TableName") != null &&
                                      typeNode.Attribute("TableName").Value == typeName
                                select typeNode).DefaultIfEmpty().First();

                //If nothing has yet been selected
                if (selectedNode == null)
                {
                    //Get node matching type
                    selectedNode = (from typeNode in _configXml.Root.Elements().Where(mapping =>
                                       mapping.Name == "DataMapping" &&
                                       (String.IsNullOrEmpty(_siteCollectionName) ?
                                       (mapping.Attribute("SiteCollection") == null ||
                                       String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                       (mapping.Attribute("SiteCollection") != null &&
                                       mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                    where typeNode.Attribute("Name").Value == typeName 
                                    select typeNode).DefaultIfEmpty().First();
                }
            }

            IEnumerable<AssociationField> assocations =
                from field in selectedNode.Descendants("Field")
                where field.Attribute("AssociationName") != null
                select new AssociationField
                {
                    AssociationName = (string)field.Attribute("AssociationName"),
                    AssociatedTypeName = (string)field.Attribute("AssociatedList"),
                    Name = (string)field.Attribute("Name"),
                };

            return assocations;
        }

        /// <summary>
        /// Gets a Type's custom attribute by name for the given type id or type name.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="typeName"></param>
        /// <param name="attrbuteName"></param>
        /// <returns></returns>
        public override string GetTypeAttribute(string id, string typeName, string attributeName)
        {
            ConfirmDefaultDataMapping();

            Guid guidId = Guid.Empty;
            string selectedValue = null;

            //Selection precedence follows:
            //If ID is present and a valid, Get type that matches ID
            //If no type matches ID, Get type that has TableName matching the typeName
            //Finally, if nothing yet has been selected get the first node that has Name matching the 
            //typeName

            //Check if ID is present and valid
            if (!String.IsNullOrEmpty(id) && _guidRegex.IsMatch(id))
            {
                guidId = new Guid(id);

                selectedValue = (string)(from typeNode in _configXml.Root.Elements().Where(mapping =>
                                           mapping.Name == "DataMapping" &&
                                           (String.IsNullOrEmpty(_siteCollectionName) ?
                                           (mapping.Attribute("SiteCollection") == null ||
                                           String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                           (mapping.Attribute("SiteCollection") != null &&
                                           mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                         where typeNode.Attribute("ID") != null &&
                                               typeNode.Attribute("ID").Value == guidId.ToString() 
                                         select typeNode.Attribute(attributeName)).DefaultIfEmpty().First() ?? "";
            }

            if (String.IsNullOrEmpty(selectedValue))
            {
                //Get node matching TableName
                selectedValue = (string)(from typeNode in _configXml.Root.Elements().Where(mapping =>
                                           mapping.Name == "DataMapping" &&
                                           (String.IsNullOrEmpty(_siteCollectionName) ?
                                           (mapping.Attribute("SiteCollection") == null ||
                                           String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                           (mapping.Attribute("SiteCollection") != null &&
                                           mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                         where typeNode.Attribute("TableName") != null &&
                                               typeNode.Attribute("TableName").Value == typeName
                                         select typeNode.Attribute(attributeName)).DefaultIfEmpty().First() ?? "";

                //If nothing has yet been selected
                if (String.IsNullOrEmpty(selectedValue))
                {
                    //Get node matching type
                    selectedValue = (string)(from typeNode in _configXml.Root.Elements().Where(mapping =>
                                               mapping.Name == "DataMapping" &&
                                               (String.IsNullOrEmpty(_siteCollectionName) ?
                                               (mapping.Attribute("SiteCollection") == null ||
                                               String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                               (mapping.Attribute("SiteCollection") != null &&
                                               mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                             where typeNode.Attribute("Name").Value == typeName
                                             select typeNode.Attribute(attributeName)).DefaultIfEmpty().First() ?? "";
                }
            }

            return selectedValue;
        }

        /// <summary>
        /// Gets fields that are not associations for the given type id or name.
        /// </summary>
        /// <param name="id">Optional unique SharePoint List Guid id as string</param>
        /// <param name="typeName">Name of List or ContentType</param>
        /// <returns></returns>
        public override IEnumerable<Field> GetTypeFields(string id, string typeName)
        {
            ConfirmDefaultDataMapping();

            Guid guidId = Guid.Empty;
            XElement selectedNode = null;

            //Selection precedence follows:
            //If ID is present and a valid, Get type that matches ID
            //If no type matches ID, Get type that has TableName matching the typeName
            //Finally, if nothing yet has been selected get the first node that has Name matching the 
            //typeName

            //Check if ID is present and valid
            if (!String.IsNullOrEmpty(id) && _guidRegex.IsMatch(id))
            {
                guidId = new Guid(id);

                selectedNode = (from typeNode in _configXml.Root.Elements().Where(mapping =>
                                   mapping.Name == "DataMapping" &&
                                   (String.IsNullOrEmpty(_siteCollectionName) ?
                                   (mapping.Attribute("SiteCollection") == null ||
                                   String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                   (mapping.Attribute("SiteCollection") != null &&
                                   mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                where typeNode.Attribute("ID") != null &&
                                      typeNode.Attribute("ID").Value == guidId.ToString()
                                select typeNode).DefaultIfEmpty().First();
            }

            if (selectedNode == null)
            {
                //Get node matching TableName
                selectedNode = (from typeNode in _configXml.Root.Elements().Where(mapping =>
                                   mapping.Name == "DataMapping" &&
                                   (String.IsNullOrEmpty(_siteCollectionName) ?
                                   (mapping.Attribute("SiteCollection") == null ||
                                   String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                   (mapping.Attribute("SiteCollection") != null &&
                                   mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                where typeNode.Attribute("TableName") != null &&
                                      typeNode.Attribute("TableName").Value == typeName 
                                select typeNode).DefaultIfEmpty().First();

                //If nothing has yet been selected
                if (selectedNode == null)
                {
                    //Get node matching type
                    selectedNode = (from typeNode in _configXml.Root.Elements().Where(mapping =>
                                       mapping.Name == "DataMapping" &&
                                       (String.IsNullOrEmpty(_siteCollectionName) ?
                                       (mapping.Attribute("SiteCollection") == null ||
                                       String.IsNullOrEmpty(mapping.Attribute("SiteCollection").Value)) :
                                       (mapping.Attribute("SiteCollection") != null &&
                                       mapping.Attribute("SiteCollection").Value.ToLower() == _siteCollectionName.ToLower()))).Elements()
                                    where typeNode.Attribute("Name").Value == typeName 
                                    select typeNode).DefaultIfEmpty().First();
                }
            }

            IEnumerable<SqlMappedField> fields =
                from field in selectedNode.Descendants("Field")
                where field.Attribute("AssociationName") == null
                select new SqlMappedField
                {
                    Column = field.Attribute("Column") == null ? "" : (string)field.Attribute("Column"),
                    Name = field.Attribute("Name") == null ? "" : (string)field.Attribute("Name"),
                    SharePointType = field.Attribute("SPType") == null ? "" : (string)field.Attribute("SPType"),
                    SqlType = field.Attribute("SqlType") == null ? "" : (string)field.Attribute("SqlType"),
                    Required = field.Attribute("Required") == null ? false : (string)field.Attribute("Required").Value.ToLower() == "true",
                };

            return fields.Cast<Field>().ToList();

        }

        private void ConfirmDefaultDataMapping()
        {
            if (_defaultDataMapping && !_defaultConfirmed)
            {
                XElement selectedNode = (from node in _configXml.Root.Elements()
                                         where node.Name == "DataMapping" &&
                                                 node.Attribute("SiteCollection") == null
                                         select node).DefaultIfEmpty().First();

                if (selectedNode == null)
                {
                    selectedNode = (from node in _configXml.Root.Elements()
                                    where node.Name == "DataMapping"
                                    select node).DefaultIfEmpty().First();

                    _siteCollectionName = selectedNode.Attribute("SiteCollection").Value;
                }

                _defaultConfirmed = true;
            }
        }
    }

    
}
